﻿' LibWowAPI
' by Ronald M. Clifford (roncli@roncli.com)
'
' This source code is released under the GNU Library General Public License (LGPL) Version 2.1.

Imports System
Imports System.Collections.Specialized
Imports System.Globalization
Imports System.IO
Imports System.Net
Imports System.Security.Cryptography
Imports System.Text
Imports System.Threading
Imports System.Web
Imports Newtonsoft.Json
Imports roncliProductions.LibWowAPI.Internationalization

Namespace roncliProductions.LibWowAPI

    ''' <summary>
    ''' The base class to inherit Armory info from
    ''' </summary>
    ''' <remarks></remarks>
    Public MustInherit Class WowAPIData

        ''' <summary>
        ''' The data that was received from the Blizzard WoW API.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property Data As String

#Region "Properties"

#Region "Public"

        ''' <summary>
        ''' The region the request is for.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property Region As Region = Region.NorthAmerica

        ''' <summary>
        ''' The language the request is for.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property Language As Language = Language.EnglishUS

        ''' <summary>
        ''' The public key to use for authentication.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property PublicKey As String

        ''' <summary>
        ''' The private key to use for authentication.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property PrivateKey As String

        ''' <summary>
        ''' The name of the application.  This gets passed as an HTTP header.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property Application As String

        ''' <summary>
        ''' The URL of the application.  This gets passed as an HTTP header.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Property ApplicationURL As String

        Private blnCacheHit As New Boolean?
        ''' <summary>
        ''' A boolean to indicate whether there was a cache hit within LibWowAPI.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property CacheHit As Boolean?
            Get
                Return blnCacheHit
            End Get
        End Property

        Private dtIfModifiedSince As Date = Date.MinValue
        ''' <summary>
        ''' The date the information was last modified.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overridable Property IfModifiedSince As Date
            Get
                Return dtIfModifiedSince
            End Get
            Set(value As Date)
                dtIfModifiedSince = value
            End Set
        End Property

        ''' <summary>
        ''' The length of time the data should be cached for, defaulting to 1 day.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overridable Property CacheLength As New TimeSpan(1, 0, 0, 0)

#End Region

#Region "Protected"

        Private qsQueryString As New NameValueCollection()
        Protected ReadOnly Property QueryString As NameValueCollection
            Get
                Return qsQueryString
            End Get
        End Property

        Protected MustOverride ReadOnly Property URI As Uri

        Protected MustOverride ReadOnly Property CacheKey As String

        Private ReadOnly Property InternalCacheKey As String
            Get
                Return String.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", CacheKey, CInt(Region), CInt(Language))
            End Get
        End Property

#End Region

#End Region

#Region "Methods"

        Protected Shared Function GetBaseURI() As Uri
            Select Case Region
                Case Region.China
                    Return New Uri("https://battlenet.com.cn/")
                Case Region.Europe
                    Return New Uri("https://eu.battle.net/")
                Case Region.Korea
                    Return New Uri("https://kr.battle.net/")
                Case Region.Taiwan
                    Return New Uri("https://tw.battle.net/")
                Case Else
                    Return New Uri("https://us.battle.net/")
            End Select
        End Function

        Protected Shared Function GetLocale() As String
            Select Case Language
                Case Language.Deutsch
                    Return "de_DE"
                Case Language.EnglishEU
                    Return "en_EU"
                Case Language.EspañolAL
                    Return "es_MX"
                Case Language.EspañolEU
                    Return "es_ES"
                Case Language.Français
                    Return "fr_FR"
                Case Language.Русский
                    Return "ru_RU"
                Case Language.한국어
                    Return "ko_KR"
                Case Language.简体中文
                    Return "zh_CN"
                Case Language.繁體中文
                    Return "zh_TW"
                Case Else
                    Return "en_US"
            End Select
        End Function

        ''' <summary>
        ''' Retrieves the data based on the supplied query
        ''' </summary>
        ''' <remarks></remarks>
        Protected Sub Retrieve()
            If Cache.Cache(InternalCacheKey) Is Nothing Then
                blnCacheHit = False

                Dim uriPath = New Uri(GetBaseURI, URI)
                Dim uqsURI As New URIQueryStringCollection(uriPath)
                For Each strKey As String In QueryString.Keys
                    uqsURI.Add(strKey, QueryString(strKey))
                Next
                uqsURI.Add("locale", GetLocale())

                Dim hwrRequest = CType(HttpWebRequest.Create(uqsURI.ToURI()), HttpWebRequest)
                If IfModifiedSince <> Date.MinValue Then
                    hwrRequest.Headers.Add(HttpRequestHeader.LastModified, String.Format(CultureInfo.InvariantCulture, "{0:ddd, dd MMM yyyy hh:mm:ss} GMT", TimeZoneInfo.ConvertTimeToUtc(IfModifiedSince)))
                End If
                hwrRequest.Timeout = 10000

                hwrRequest.Headers.Add("X-Library", "LibWowAPI")
                hwrRequest.Headers.Add("X-LibraryURL", "http://libwowarmory.codeplex.com")
                If Not String.IsNullOrWhiteSpace(Application) Then
                    hwrRequest.Headers.Add("X-Application", Application)
                End If
                If Not String.IsNullOrWhiteSpace(ApplicationURL) Then
                    hwrRequest.Headers.Add("X-ApplicationURL", ApplicationURL)
                End If

                If Not (String.IsNullOrWhiteSpace(PublicKey) And String.IsNullOrWhiteSpace(PrivateKey)) Then
                    Dim strEncoding As String
                    hwrRequest.Date = Date.UtcNow

                    Dim strStringToSign = String.Format(CultureInfo.InvariantCulture, "GET{0}{1:r}{0}{2}{0}", Convert.ToChar(10), hwrRequest.Date.ToUniversalTime(), uriPath.AbsolutePath)
                    Using shaEncryptor As New HMACSHA1(Encoding.UTF8.GetBytes(PrivateKey))
                        strEncoding = String.Format(CultureInfo.InvariantCulture, "BNET {0}:{1}", PublicKey, Convert.ToBase64String(shaEncryptor.ComputeHash(Encoding.UTF8.GetBytes(strStringToSign))))
                    End Using
                    hwrRequest.Headers.Add(HttpRequestHeader.Authorization, strEncoding)
                End If
                Try
                    Using wrResponse = hwrRequest.GetResponse
                        Using srReader As New StreamReader(wrResponse.GetResponseStream)
                            Data = srReader.ReadToEnd
                        End Using
                        For Each cCookie As Cookie In CType(wrResponse, HttpWebResponse).Cookies
                            hwrRequest.CookieContainer.Add(cCookie)
                        Next
                    End Using

                    Cache.Cache.Add(InternalCacheKey, Data, Nothing, Date.Now.Add(CacheLength), Caching.Cache.NoSlidingExpiration, Caching.CacheItemPriority.NotRemovable, Nothing)
                Catch wex As WebException
                    If wex.Response Is Nothing Then Throw
                    Using srReader As New StreamReader(wex.Response.GetResponseStream)
                        Data = srReader.ReadToEnd
                    End Using
                    Dim baeError As BlizzardAPIError = CType(JsonConvert.DeserializeObject(Data, GetType(BlizzardAPIError)), BlizzardAPIError)
                    Throw New BlizzardAPIException(baeError.status, baeError.reason)
                End Try
                hwrRequest = Nothing
            Else
                blnCacheHit = True
                Data = Convert.ToString(Cache.Cache(InternalCacheKey), CultureInfo.InvariantCulture)
            End If
        End Sub

        ''' <summary>
        ''' Clears the cache associated with the current options.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Clear()
            If Cache.Cache(InternalCacheKey) IsNot Nothing Then
                Cache.Cache.Remove(InternalCacheKey)
            End If
        End Sub

#End Region

    End Class

End Namespace
